﻿// *********************************************************************
// [DCOM Productions]
// [Copyright (C) DCOM Productions All rights reserved.]
// *********************************************************************

namespace BitFlex.Windows.Forms {
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel;
    using System.Windows.Forms;
    using BitFlex.Win32.Structures;
    using System.Runtime.InteropServices;
    using BitFlex.Win32;
    using BitFlex.Win32.Constants;

    /// <summary>
    /// An application desktop toolbar (also called an appbar) is a window that is similar to the Windows taskbar. It is anchored to an edge of the screen, and it typically contains buttons that give the user quick access to other applications and windows. The system prevents other applications from using the desktop area occupied by an appbar. Any number of appbars can exist on the desktop at any given time.
    /// </summary>
    public partial class AppBar : Form {

        #region Fields

        /// <devdoc>
        /// This message is registered as "AppBarMessage" and the field stores the identifier registered in Windows
        /// </devdoc>
        private uint uCallbackMessage = 0;

        /// <summary>
        /// This is a quick field initializer
        /// Mainly used so when the AppBar is run for the first time, the size and position is updated correctly based on
        /// how the designer has specified the height and width and design-time
        /// </summary>
        private bool initialized = false;

        #endregion

        #region Constants

        /// <devdoc>
        /// Custom Windows Message
        /// Used to register our AppBar Windows Message and create a callback identifier
        /// </devdoc>
        private const string AppBarMessage = "AppBarMessage";

        #endregion

        #region Properties

        private bool m_AutoHide = false;
        /// <summary>
        /// Gets or sets a System.Boolean value indicating whether the AppBar is an AutoHide AppBar
        /// </summary>
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Gets or sets a System.Boolean value indicating whether the AppBar is an AutoHide AppBar")]
        public bool AutoHide {
            get {
                return m_AutoHide;
            }
            set {
                m_AutoHide = value;
            }
        }

        /// <summary>
        /// New implementation for base.CreateParams
        /// </summary>
        protected override System.Windows.Forms.CreateParams CreateParams {
            get {
                CreateParams cp = base.CreateParams;
                cp.Style &= (~0x00C00000); // WS_CAPTION
                cp.Style &= (~0x00800000); // WS_BORDER
                cp.ExStyle = 0x00000080 | 0x00000008; // WS_EX_TOOLWINDOW | WS_EX_TOPMOST
                return cp;
            }
        }

        /// <summary>
        /// Hides base implementation of Control.Dock
        /// </summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override DockStyle Dock {
            get {
                return base.Dock;
            }
            set {
                base.Dock = value;
            }
        }

        private ABEdge m_Edge = ABEdge.ABE_TOP; // Default Edge
        /// <summary>
        /// Gets or sets a <see cref="BitFlex.Win32.Constants.ABEdge"/> enumeration value that specifies which edge of the screen the AppBar will attach to
        /// </summary>
        [Browsable(true)]
        [Category("Layout")]
        [DefaultValue(ABEdge.ABE_TOP)]
        [Description("Gets or sets a BitFlex.Win32.Constants.ABEdge enumeration value that specifies which edge of the screen the AppBar will attach to")]
        [EditorBrowsable(EditorBrowsableState.Always)]
        public ABEdge Edge {
            get {
                return m_Edge;
            }
            set {
                ABEdge oldValue = m_Edge;
                ABEdge newValue = value;
                m_Edge = newValue;
                if (!DesignMode && initialized)
                    AdjustWindowSize(newValue, oldValue);
            }
        }

        private void AdjustWindowSize(ABEdge newValue, ABEdge oldValue) {
            if (oldValue == ABEdge.ABE_LEFT || oldValue == ABEdge.ABE_RIGHT) {
                if (newValue == ABEdge.ABE_BOTTOM || newValue == ABEdge.ABE_TOP) {
                    int oldWidth = Width;
                    int oldHeight = Height;
                    Width = oldHeight;
                    Height = oldWidth;
                }
            }
            if (oldValue == ABEdge.ABE_BOTTOM || oldValue == ABEdge.ABE_TOP) {
                if (newValue == ABEdge.ABE_LEFT || newValue == ABEdge.ABE_RIGHT) {
                    int oldWidth = Width;
                    int oldHeight = Height;
                    Width = oldHeight;
                    Height = oldWidth;
                }
            }
            AppBarPositionChanged();
        }

        private bool m_Registered = false;
        /// <summary>
        /// Gets a System.Boolean value indicating whether the AppBar is registered
        /// </summary>
        [Browsable(false)]
        public bool Registered {
            get {
                return m_Registered;
            }
        }

        #endregion

        #region AppBar Registration

        /// <summary>
        /// Registers the AppBar
        /// </summary>
        /// <returns>Returns true if the AppBar was successfully registered, otherwise returns false</returns>
        public bool Register() {
            if (!Registered) {
                return AutoHide ? RegisterAutoHideAppBar() : RegisterAppBar();
            }
            else {
                return false;
            }
        }

        private bool RegisterAppBar() {
            APPBARDATA pData = new APPBARDATA();
            pData.cbSize = Marshal.SizeOf(pData);
            pData.hWnd = Handle;
            uCallbackMessage = NativeMethods.RegisterWindowMessage(AppBarMessage);
            pData.uCallbackMessage = uCallbackMessage;
            UIntPtr uint_ptr = NativeMethods.SHAppBarMessage((int)ABMessage.ABM_NEW, ref pData);
            QueryAndPositionAppBar();
            m_Registered = uint_ptr.ToUInt32() == 1 ? true : false;
            return uint_ptr.ToUInt32() == 1 ? true : false;
        }

        private bool RegisterAutoHideAppBar() {
            APPBARDATA pData = new APPBARDATA();
            pData.cbSize = Marshal.SizeOf(pData);
            pData.hWnd = Handle;
            uCallbackMessage = NativeMethods.RegisterWindowMessage(AppBarMessage);
            pData.uCallbackMessage = uCallbackMessage;
            UIntPtr uint_ptr = NativeMethods.SHAppBarMessage((int)ABMessage.ABM_SETAUTOHIDEBAR, ref pData);
            QueryAndPositionAppBar();
            m_Registered = uint_ptr.ToUInt32() == 1 ? true : false;
            return uint_ptr.ToUInt32() == 1 ? true : false;
        }

        /// <summary>
        /// Unregisters the AppBar
        /// </summary>
        /// <returns>Returns true if the AppBar was successfully unregistered, otherwise returns false</returns>
        public bool Unregister() {
            if (Registered) {
                APPBARDATA pData = new APPBARDATA();
                pData.cbSize = Marshal.SizeOf(pData);
                pData.hWnd = this.Handle;
                UIntPtr uint_ptr = NativeMethods.SHAppBarMessage((int)ABMessage.ABM_REMOVE, ref pData);
                m_Registered = false;
                return uint_ptr.ToUInt32() == 1 ? true : false; // Always returns TRUE
            }
            else {
                return false;
            }
        }

        #endregion

        #region AppBar Size and Positioning

        private void AppBarPositionChanged() {
            APPBARDATA pData = new APPBARDATA();
            pData.cbSize = Marshal.SizeOf(pData);
            pData.hWnd = Handle;
            RECT rc = new RECT();
            RECT rcWindow = new RECT();
            int iHeight;
            int iWidth;
            rc.top = 0;
            rc.left = 0;
            const int SM_CXSCREEN = 0;
            rc.right = NativeMethods.GetSystemMetrics(SM_CXSCREEN);
            const int SM_CYSCREEN = 1;
            rc.bottom = NativeMethods.GetSystemMetrics(SM_CYSCREEN);
            NativeMethods.GetWindowRect(pData.hWnd, out rcWindow);
            iHeight = rcWindow.bottom - rcWindow.top;
            iWidth = rcWindow.right - rcWindow.left;
            switch (Edge) {
                case ABEdge.ABE_TOP:
                    rc.bottom = rc.top + iHeight;
                    break;
                case ABEdge.ABE_BOTTOM:
                    rc.top = rc.bottom - iHeight;
                    break;
                case ABEdge.ABE_LEFT:
                    rc.right = rc.left + iWidth;
                    break;
                case ABEdge.ABE_RIGHT:
                    rc.left = rc.right - iWidth;
                    break;
            }
            QueryAndPositionAppBar();
        }

        protected override void OnResizeEnd(EventArgs e) {
            AppBarPositionChanged();
        }

        /// <devdoc>
        /// Queries Positions the AppBar into position on the Screen
        /// </devdoc>
        private void QueryAndPositionAppBar( /* UINT uEdge, LPRECT lprc, PAPPBARDATA pabd */ ) {
            const int SM_CYSCREEN = 1;
            const int SM_CXSCREEN = 0;
            int iHeight = 0;
            int iWidth = 0;
            APPBARDATA pData = new APPBARDATA();
            pData.cbSize = Marshal.SizeOf(pData);
            pData.hWnd = Handle;
            pData.uEdge = (uint)Edge;
            switch (pData.uEdge) {
                case (uint)ABEdge.ABE_LEFT:
                    pData.rc.left = 0;
                    pData.rc.right = Bounds.Width;
                    break;
                case (uint)ABEdge.ABE_RIGHT:
                    pData.rc.right = NativeMethods.GetSystemMetrics(SM_CXSCREEN);
                    pData.rc.left = NativeMethods.GetSystemMetrics(SM_CXSCREEN) - Bounds.Width;
                    break;
                case (uint)ABEdge.ABE_TOP:
                    pData.rc.top = 0;
                    pData.rc.bottom = Bounds.Height;
                    break;
                case (uint)ABEdge.ABE_BOTTOM:
                    pData.rc.top = NativeMethods.GetSystemMetrics(SM_CYSCREEN) - Bounds.Height;
                    pData.rc.bottom = NativeMethods.GetSystemMetrics(SM_CYSCREEN);
                    break;
            }
            if (Edge == ABEdge.ABE_LEFT || Edge == ABEdge.ABE_RIGHT) {
                iWidth = pData.rc.right - pData.rc.left;
                pData.rc.top = 0;
                pData.rc.bottom = NativeMethods.GetSystemMetrics(SM_CYSCREEN);
            }
            else {
                iHeight = pData.rc.bottom - pData.rc.top;
                pData.rc.left = 0;
                pData.rc.right = NativeMethods.GetSystemMetrics(SM_CXSCREEN);
            }
            NativeMethods.SHAppBarMessage((int)ABMessage.ABM_QUERYPOS, ref pData);
            switch (Edge) {
                case ABEdge.ABE_LEFT:
                    pData.rc.right = pData.rc.left + iWidth;
                    break;
                case ABEdge.ABE_RIGHT:
                    pData.rc.left = pData.rc.right - iWidth;
                    break;
                case ABEdge.ABE_TOP:
                    pData.rc.bottom = pData.rc.top + iHeight;
                    break;
                case ABEdge.ABE_BOTTOM:
                    pData.rc.top = pData.rc.bottom - iHeight;
                    break;
            }
            NativeMethods.SHAppBarMessage((int)ABMessage.ABM_SETPOS, ref pData);
            NativeMethods.MoveWindow(pData.hWnd,
                pData.rc.left,
                pData.rc.top,
                pData.rc.right - pData.rc.left,
                pData.rc.bottom - pData.rc.top,
                true);
        }

        #endregion

        #region AppBar Message Processing

        private void AppBarFullScreenApp(bool lParam) {
            IntPtr HWND_TOPMOST = new IntPtr(-1);
            IntPtr HWND_BOTTOM = new IntPtr(1);
            const int SWP_NOMOVE = 0x0002;
            const int SWP_NOSIZE = 0x0001;
            const int SWP_NOACTIVATE = 0x0010;
            APPBARDATA pData = new APPBARDATA();
            pData.cbSize = Marshal.SizeOf(pData);
            pData.hWnd = Handle;
            UIntPtr uint_ptr = NativeMethods.SHAppBarMessage((int)ABMessage.ABM_GETSTATE, ref pData);
            uint uState = uint_ptr.ToUInt32();
            if (lParam) {
                if (uState == (uint)ABState.ABS_ALWAYSONTOP) {
                    NativeMethods.SetWindowPos(pData.hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                }
                else {
                    NativeMethods.SetWindowPos(pData.hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                }
            }
            else {
                if (uState == (uint)ABState.ABS_ALWAYSONTOP) {
                    NativeMethods.SetWindowPos(pData.hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                }
            }
        }

        private void AppBarStateChanged() {
            APPBARDATA pData = new APPBARDATA();
            pData.cbSize = Marshal.SizeOf(pData);
            pData.hWnd = Handle;
            UIntPtr uint_ptr = NativeMethods.SHAppBarMessage((int)ABMessage.ABM_GETSTATE, ref pData);
            uint uState = uint_ptr.ToUInt32();
            IntPtr HWND_TOPMOST = new IntPtr(-1);
            IntPtr HWND_BOTTOM = new IntPtr(1);
            const int SWP_NOMOVE = 0x0002;
            const int SWP_NOSIZE = 0x0001;
            const int SWP_NOACTIVATE = 0x0010;
            if (uState == (uint)ABState.ABS_ALWAYSONTOP) {
                NativeMethods.SetWindowPos(pData.hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
            }
            else {
                NativeMethods.SetWindowPos(pData.hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
            }
        }

        protected override void WndProc(ref Message m) {
            if (m.Msg == (int)uCallbackMessage) {
                switch (m.WParam.ToInt32()) {
                    case (int)ABNotification.ABN_STATECHANGE:
                        AppBarStateChanged();
                        break;
                    case (int)ABNotification.ABN_FULLSCREENAPP:
                        if (m.LParam.ToInt32() == 1) // TRUE, C++ Specification
                            AppBarFullScreenApp(true);
                        if (m.LParam.ToInt32() != 1) // FALSE, C++ Specification
                            AppBarFullScreenApp(false);
                        break;
                    case (int)ABNotification.ABN_POSCHANGED:
                        AppBarPositionChanged();
                        break;
                }
            }
            else {
                base.WndProc(ref m);
            }
        }

        #endregion

        #region Control Loading and Unloading

        /// <summary>
        /// Registers the AppBar when the form is loaded
        /// </summary>
        protected override void OnLoad(EventArgs e) {
            if (!DesignMode) {
                Register();
                initialized = true;
            }
        }

        /// <summary>
        /// Unregisters the AppBar as the form is closed
        /// </summary>
        protected override void OnClosing(CancelEventArgs e) {
            if (!DesignMode) {
                Unregister();
            }
        }

        #endregion
    }
}